/**
   @file memory.c
   @brief The memory workload.
 */

#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include "cJSON.h"
#include "random.h"
#include "memory.h"
#include "scheduler.h"
#include "generator.h"

#define ONE p=(char **)*p;
#define FIVE ONE ONE ONE ONE ONE
#define TEN FIVE FIVE
#define FIFTY TEN TEN TEN TEN TEN
#define HUNDRED FIFTY FIFTY

#define	DOIT(i)	p[i]=

mode *setup_memory_read_random(cJSON *);
mode *setup_memory_write_random(cJSON *);
mode *setup_memory_read_sequential(cJSON *);
mode *setup_memory_write_sequential(cJSON *);

void *memory_read_rand(void *);

void *memory_memory_write_rand(void *);

void *memory_memory_read_sequential(void *);

void *memory_write_sequential(void *);

/** Initialize memory_buffer from a JSON object. */
memory_generator_state *setup_memory_blocks(cJSON *);

/** Hash table to store supported modes for the memory workload. */
mode_definition_list *memory_modes[HASHSIZE];

/** Spawn a memory job and schedule the next arrival. */
void create_memory_workload(void *);

size_t*
permutation(size_t max, size_t scale)
{
	size_t	i, v, o;
	static size_t r = 0;
	size_t*	result = (size_t*)malloc(max * sizeof(size_t));

	if (result == NULL) return NULL;

	for (i = 0; i < max; ++i) {
		result[i] = i * scale;
	}

	if (r == 0)
		r = (getpid()<<6) ^ getppid() ^ rand() ^ (rand()<<10);

	/* randomize the sequence */
	for (i = 0; i < max; ++i) {
		/**
                  This isn't a random permutation
		*/
		r = (r << 1) ^ rand();
		o = r % max;
		v = result[o];
		result[o] = result[i];
		result[i] = v;
	}
        return result;
}

void
base_initialize(iter_t iterations, void* cookie)
{
	size_t	nwords, nlines, nbytes, npages, nmpages;
	size_t *pages;
	size_t *lines;
	size_t *words;
	struct mem_state* state = (struct mem_state*)cookie;
	register char *p = 0 /* lint */;

	if (iterations) return;

	state->initialized = 0;

	nbytes = state->len;
	nwords = state->line / sizeof(char*);
	nlines = state->pagesize / state->line;
	npages = (nbytes + state->pagesize - 1) / state->pagesize;
	nmpages= (state->maxlen + state->pagesize - 1) / state->pagesize;

	srand(getpid());

	words = NULL;
	lines = NULL;
	pages = permutation(nmpages, state->pagesize);
	p = state->addr = (char*)malloc(state->maxlen + 2 * state->pagesize);

	state->nwords = nwords;
	state->nlines = nlines;
	state->npages = npages;
	state->lines = lines;
	state->pages = pages;
	state->words = words;

	if (state->addr == NULL || pages == NULL)
		return;

	if ((unsigned long)p % state->pagesize) {
		p += state->pagesize - (unsigned long)p % state->pagesize;
	}
	state->base = p;
	state->initialized = 1;
}


/*
 * Create a circular list of pointers using a simple striding
 * algorithm.
 * 
 * This access pattern corresponds to many array/matrix
 * algorithms.  It should be easily and correctly predicted
 * by any decent hardware prefetch algorithm.
 */
void
stride_initialize(iter_t iterations, void* cookie)
{
  struct mem_state* state = (struct mem_state*)cookie;
  size_t	i;
  size_t	range = state->len;
  size_t	stride = state->line;
  char*	addr;
  
  base_initialize(iterations, cookie);
  if (!state->initialized) return;
  addr = state->base;
  
  for (i = stride; i < range; i += stride) {
    *(char **)&addr[i - stride] = (char*)&addr[i];
  }
  *(char **)&addr[i - stride] = (char*)&addr[0];
  state->p[0] = addr;
}

/*
 * words_initialize
 *
 * This is supposed to create the order in which the words in a
 * "cache line" are used.  Since we rarely know the cache line
 * size with any real reliability, we need to jump around so
 * as to maximize the number of potential cache misses, and to
 * minimize the possibility of re-using a cache line.
 */
size_t*
words_initialize(size_t max, int scale)
{
	size_t	i, j, nbits;
	size_t*	words = (size_t*)malloc(max * sizeof(size_t));

	memset(words,0, max * sizeof(size_t));
	for (i = max>>1, nbits = 0; i != 0; i >>= 1, nbits++)
		;
	for (i = 0; i < max; ++i) {
		/* now reverse the bits */
		for (j = 0; j < nbits; j++) {
			if (i & (1<<j)) {
				words[i] |= (1<<(nbits-j-1));
			}
		}
		words[i] *= scale;
	}
	return words;
}

void
thrash_initialize(iter_t iterations, void* cookie)
{
	struct mem_state* state = (struct mem_state*)cookie;
	size_t	i;
	size_t	j;
	size_t	cur;
	size_t	next;
	size_t	cpage;
	size_t	npage;
	char*	addr;

	base_initialize(iterations, cookie);
	if (!state->initialized) return;
	addr = state->base;

	/*
	 * Create a circular list of pointers with a random access
	 * pattern.
	 *
	 * This stream corresponds more closely to linked list
	 * memory access patterns.  For large data structures each
	 * access will likely cause both a cache miss and a TLB miss.
	 * 
	 * Access a different page each time.  This will eventually
	 * cause a tlb miss each page.  It will also cause maximal
	 * thrashing in the cache between the user data stream and
	 * the page table entries.
	 */
	if (state->len % state->pagesize) {
		state->nwords = state->len / state->line;
		state->words = words_initialize(state->nwords, state->line);
		if (!state->words) {
			perror("thrash_initialize: malloc");
			exit(1);
		}
		for (i = 0; i < state->nwords - 1; ++i) {
			*(char **)&addr[state->words[i]] = (char*)&addr[state->words[i+1]];
		}
		*(char **)&addr[state->words[i]] = addr;
		state->p[0] = addr;
	} else {
		state->nwords = state->pagesize / state->line;
		state->words = words_initialize(state->nwords, state->line);
		if (!state->words) {
			perror("thrash_initialize: malloc");
			exit(2);
		}
		for (i = 0; i < state->npages - 1; ++i) {
			cpage = state->pages[i];
			npage = state->pages[i + 1];
			for (j = 0; j < state->nwords; ++j) {
				cur = cpage + state->words[(i + j) % state->nwords];
				next = npage + state->words[(i + j + 1) % state->nwords];
				*(char **)&addr[cur] = (char*)&addr[next];
			}
		}
		cpage = state->pages[i];
		npage = state->pages[0];
		for (j = 0; j < state->nwords; ++j) {
			cur = cpage + state->words[(i + j) % state->nwords];
			next = npage + state->words[(j + 1) % state->nwords];
			*(char **)&addr[cur] = (char*)&addr[next];
		}
		state->p[0] = (char*)&addr[state->pages[0]];
	}
}

memory_generator_state * initialize_memory(unsigned long size, unsigned int flags) {
  memory_generator_state *state = malloc(sizeof(memory_generator_state));
  
  state->sequential.width = 1;
  state->sequential.len = size;
  state->sequential.maxlen = size;
  state->sequential.line = STRIDE;
  state->sequential.pagesize = getpagesize();
  
  state->random.width = 1;
  state->random.len = size;
  state->random.maxlen = size;
  state->random.line = STRIDE;
  state->random.pagesize = getpagesize();

  if(flags & GEN_SEQ) {
    stride_initialize(0,&state->sequential);
  }
  if(flags & GEN_RAND) {
    thrash_initialize(0,&state->random);
  }
  return state;
}

/**
   @param state the state of the memory to read from
   @param bytes The number of bytes to read from memory.
 */
int memory_read_bytes(mem_state *state, unsigned long bytes) {

  register char **p = (char**)state->p[0];
  register size_t i;
  register size_t count = bytes / (state->line * 100) + 1;

  for(i = 0; i < count; ++i) {
    HUNDRED;
  }
  
  state->p[0] = (char*)p;

  return 0;
}

/** 
    @param state The state of memory to write to
    @param bytes The number of bytes to read from memory. 
 */
int memory_write_bytes(mem_state *state, unsigned long bytes) {
  register char *s;
  register char **p = (char **)state->p[0];
  register size_t i;
  register size_t count = bytes / state->line;
  
  for(i = 0; i < count; ++i) {
    DOIT(1) DOIT(2) DOIT(3) DOIT(4) DOIT(5) DOIT(6)
      DOIT(7) DOIT(8) DOIT(9) DOIT(10) DOIT(11) DOIT(12)
      DOIT(13) DOIT(14) DOIT(15) DOIT(16) DOIT(17) DOIT(18)
      DOIT(19) DOIT(20) DOIT(21) DOIT(22) DOIT(23) DOIT(24)
      DOIT(25) DOIT(26) DOIT(27) DOIT(28) DOIT(29) DOIT(30)
      DOIT(31) DOIT(32) DOIT(33) DOIT(34) DOIT(35) DOIT(36)
      DOIT(37) DOIT(38) DOIT(39) DOIT(40) DOIT(41) DOIT(42)
      DOIT(43) DOIT(44) DOIT(45) DOIT(46) DOIT(47) DOIT(48)
      DOIT(49) DOIT(50) DOIT(51) DOIT(52) DOIT(53) DOIT(54)
      DOIT(55) DOIT(56) DOIT(57) DOIT(58) DOIT(59) DOIT(60)
      DOIT(61) DOIT(62) s;
    p = (char**)*p;
  }
  state->p[0] = (char *)p;
  
  return 0;
}

resource_model *setup_memory(cJSON *json) {
  memory_generator_state *state;
  install_mode(memory_modes, "rr", setup_memory_read_random);
  install_mode(memory_modes, "wr", setup_memory_write_random);
  install_mode(memory_modes, "rs", setup_memory_read_sequential);
  install_mode(memory_modes, "ws", setup_memory_write_sequential);
  //Setup the interarrival rate
  setup_rng("memory_arrival", cJSON_GetObjectItem(json,"distribution"));
  //Build our Memory State
  state = setup_memory_blocks(json);
  resource_model *model = malloc(sizeof(resource_model));
  model->birth = create_memory_workload;
  model->state = (void*)state;
  model->modes = initialize_modes(memory_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

memory_generator_state * setup_memory_blocks(cJSON *json) {
  cJSON *memory_size = cJSON_GetObjectItem(json, "memory_size");
  unsigned long i_memory_size = memory_size->valuedouble*1024;
  return initialize_memory(i_memory_size, GEN_SEQ | GEN_RAND);
}

void create_memory_workload(void *args) {
  free( spawn_job((resource_model *) args) );
  schedule_event(ms_from_now(get_random_number("memory_arrival")), create_memory_workload, args);
}

void *memory_read_rand(void *args) {
  memory_generator_state *state = (memory_generator_state*)args;
  unsigned long bytes_to_read =(unsigned long) get_random_number("memory_rr_length");
  memory_read_bytes(&state->random, bytes_to_read);
}

void *memory_write_rand(void *args) {
  memory_generator_state *state = (memory_generator_state*)args;
  unsigned int bytes_to_write = (unsigned int) get_random_number("memory_wr_length");
  memory_write_bytes(&state->random, bytes_to_write);
}

void *memory_read_sequential(void *args) {
  memory_generator_state *state = (memory_generator_state*)args;
  unsigned long bytes_to_read = (unsigned long) get_random_number("memory_rs_length");
  memory_read_bytes(&state->sequential, bytes_to_read);
}

void *memory_write_sequential(void *args) {
  memory_generator_state *state = (memory_generator_state*)args;
  unsigned long bytes_to_write = (unsigned long) get_random_number("memory_ws_length");
  memory_write_bytes(&state->sequential, bytes_to_write);
}

mode *setup_memory_read_random(cJSON *json) {
  setup_rng("memory_rr_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("rr",memory_read_rand,json);
}

mode *setup_memory_write_random(cJSON *json) {
  setup_rng("memory_wr_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("wr",memory_write_rand,json);
}

mode *setup_memory_read_sequential(cJSON *json) {
  setup_rng("memory_rs_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("rs",memory_read_sequential,json);
}

mode *setup_memory_write_sequential(cJSON *json) {
  setup_rng("memory_ws_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("ws",memory_write_sequential,json);
}
